<?php

class Admin_Model_TreeNodeMapper {
	protected $_dbTable;
	
	public function setDbTable($dbTable){
        if (is_string($dbTable)) {
            $dbTable = new $dbTable();
        }
        if (!$dbTable instanceof Zend_Db_Table_Abstract) {
            throw new Exception('Invalid table data gateway provided');
        }
        $this->_dbTable = $dbTable;
        return $this;
    }

    public function getDbTable(){
        if (null === $this->_dbTable) {
            $this->setDbTable('Admin_Model_DbTable_TreeNode');
        }
        return $this->_dbTable;
    }

    /**
     * Ищет узел по Id.
     * Возвращает найденный распакованный узел или null, если узел не найден.
     * 
     * @param int $id
     * @return null|TreeNode
     */
    public function find($id,$fetchLayout=false){
    	$t1 = $this->getDbTable();
    	$sql = $t1->select();
    	if ($fetchLayout){
    		$t2 = new Admin_Model_DbTable_Layout();
    		$tableName1 = $t1->info(Zend_Db_Table_Abstract::NAME);
    		$tableName2 = $t2->info(Zend_Db_Table_Abstract::NAME);
    		//$short ? $this->_shortFieldList: 
				//protected $_shortFieldList = array('Title','MetaDescription', 'MetaKeywords', 'HTML','URL','InMenu');
    		$sql->setIntegrityCheck(false)
    			->from(array('t1'=>$tableName1),Zend_Db_Select::SQL_WILDCARD)
    			->join(array('t2'=>$tableName2), 't1.LayoutId=t2.Id','Layout')
    			->where('t1.Id = ?',$id);
    	}
    	else 
    		$sql->where('Id = ?',$id);
    	
    	$row = $t1->fetchRow($sql);
		$node = is_null($row)? null : $this->_createNode($row->toArray());
    	return $fetchLayout? array($node,is_null($row)? null : $row->Layout) : $node;
    }
    
	/**
	 * Ищет индексный узел
	 * Возвращает найденный распакованный узел или null, если узел не найден.
	 * 
	 * @return null|TreeNode
	 */
	public function findIndexPage()
    {
    	$t1 = $this->getDbTable();
    	$t2 = new Admin_Model_DbTable_Layout();
    	$tableName1 = $t1->info(Zend_Db_Table_Abstract::NAME);
    	$tableName2 = $t2->info(Zend_Db_Table_Abstract::NAME);
    	$row = $t1->fetchRow($t1->select()
    						->setIntegrityCheck(false)
    						->from(array('t1'=>$tableName1))
    						->join(array('t2'=>$tableName2), 't1.LayoutId=t2.Id','Layout')
    						->where("URL='/'")
    							);
    	$node =  is_null($row)? null : $this->_createNode($row->toArray());
    	return array($node,is_null($row)? null : $row->Layout);
    }
    
    /**
     * Возвращает массив всех узлов (распакованными).
     * 
     * @return array
     */
    public function fetchAll(){
    	$t = $this->getDbTable();
    	$rowSet = $row = $t->fetchAll(null, "LeftId DESC");
    	$nodes = array();
    	$row = $rowSet->current();
    	while($row!=null){
    		$nodes[] = $this->_createNode($row->toArray());
    		$rowSet->next();
    		$row = $rowSet->current();
    	}
    	return $nodes;
    }
    
	/**
	 * Ищет коренной узел (для отображение в дереве)
	 * Возвращает нераспакованный узел с урезанными параметрами или null, если узел не найден.
	 * @param bool $extjs
	 * @return Admin_Model_TreeNode| Admin_Model_TreeNodeExtJS в зависимости от параметра $extjs 
	 */
	public function findRoot($extjs = false){
    	$t = $this->getDbTable();
    	$row = $t->fetchRow($t->select()->from($t,array('Id','Name','LeftId','RightId'))->where("ParentId=Id"));
    	if (is_null($row)) return null;
    	if (!$extjs)
    	{
    		$entry = new Admin_Model_TreeNode();
	        $entry->setId($row->Id)->setName($row->Name);
    	}
    	else
    	{
    		$entry = new Admin_Model_TreeNodeExtJS();
	        $entry->setid($row->Id)->settext($row->Name)->setleaf(($row->LeftId+1)==$row->RightId );
    	}
    	return $entry;
    }
    
    public function fetchSubTree($node){
    	$t = $this->getDbTable();
    	$rowset = $t->fetchAll($t->select()	->where("LeftId>?",$node->getLeftId())
    										->where("RightId<?",$node->getRightId())
    										->order('LeftId')
    										);
    	$nodes = array();
    	$row = $rowset->current();
    	while($row!=null){
    		$nodes[] = $this->_createNode($row->toArray());
    		$rowset->next();
    		$row = $rowset->current();
    	}
    	return $nodes;
    }
    
	/**
	 * Возвращает массив, содержащей узлы детей первого уровня.
	 * Если $extjs ложь - вес узлы распакованы.
	 * 
	 * @param int $parentId идентификатор родительского узла
	 * @param bool $extjs истина, если вернуть массив для ExtJs, ложь в противном случае 
	 * @return array - массив узлов
	 */
	public function fetchChildrens($parentId, $extjs = false){
    	$t = $this->getDbTable();
    	$sql = $extjs ? $t->select()->from($t,array('Id','Name','LeftId','RightId')): $t->select();
    	$sql->where("ParentId={$parentId} AND ParentId<>Id");
    	$resultSet = $t->fetchAll($sql);
    	$entries   = array();
        foreach ($resultSet as $row) {
        	if ($extjs)
        	{
        		$entry = new Admin_Model_TreeNodeExtJS();
	            $entry->setid($row->Id)
	        		  ->settext($row->Name)
	        		  ->setleaf(($row->LeftId+1)==$row->RightId );
        	}
        	else
        	{
        		$entry = $this->_createNode($row->toArray()); //new Application_Model_TreeNode($row->toArray());
        	}
            $entries[] = $entry;
        }
        return $entries;
    }
    
    public function findMenu(Admin_Model_TreeNode $node=null,$id=-1,$UIClass=''){
    	$t = $this->getDbTable();
    	$sql = $node!=null? $t->select()
			    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )
			    			->where ( "(((Level<:Level) AND ((LeftId<:LeftId) OR (RightId>:RightId))) OR (ParentId=:Id) OR (ParentId=:ParentId)) AND (InMenu=1)" )
			    			->order ( "LeftId" )
			    			->bind ( array ('Id' => $node->getId (), 'Level' => $node->getLevel (), 'RightId' => $node->getRightId (), 'LeftId' => $node->getLeftId (), 'ParentId' => $node->getParentId () ) )
    			:$t->select()
			    			->from ( $t, Zend_Db_Select::SQL_WILDCARD )
			    			->where ( "(InMenu=1) AND (Level=(SELECT MIN(Level) FROM ".$t->info(Zend_Db_Table_Abstract::NAME)." WHERE InMenu=1))")
			    			//(((Level<:Level) AND ((LeftId<:LeftId) OR (RightId>:RightId))) OR (ParentId=:Id) OR (ParentId=:ParentId)) AND (InMenu=1)" )
			    			->order ( "LeftId" );
    	$rowSet = $t->fetchAll ( $sql );
    	
		return $this->_parseMenu ($id,$UIClass,$rowSet);
    }
    public function fetchFullMenu($id=-1,$UIClass=''){
    	$t = $this->getDbTable();
    	$rowSet = $t->fetchAll ( $t->select()->where('InMenu=1')->order ( "LeftId" ));
    	return $this->_parseMenu ($id,$UIClass,$rowSet);	
    }
    
 	protected function _parseMenu($currentPageId,$UIClass,$rowSet,$pId = -1){
    	//$id = 0;
    	$result = array();
    	$row = $rowSet->current();
    	while ($row!=null)
    	{
    		if (($pId==-1) || ($pId==$row->ParentId))
    		{
    			$page = array(
	    			'label'	=>	$row->MenuTitle,
	    			'uri'	=> $row->URL//=='/'  ? '/' : $row->URL.'/',
	    		);
	    		if (($currentPageId>-1) && ($row->Id==$currentPageId))
				{
					$page['active'] = true;
					if (!empty($UIClass))
						$page['class'] = $UIClass;
				}
	    			
	    		$rowSet->next();
	    		$nextRow = $rowSet->current();
	    		if ((null!=$nextRow) && ($nextRow->ParentId==$row->Id))
		    	{
		    		$page['pages'] = $this->_parseMenu($currentPageId,$UIClass,$rowSet,$row->Id);
		    		$row = $rowSet->current();
		    	}
		    	else
		    		$row = $nextRow;
    		}
    		else
    			break;
    		$result[] = $page;
    	}
		return $result;
    }
    
    protected function _parseMenuOld($activePageId,$rowSet){
    	$id = 0;
    	$result = array();
    	$row = $rowSet->current();
    	while ($row!=null)
    	{
    		$page = array(
	    			'label'	=>	$row->Name,
	    			'uri'	=> $row->URL//=='/'  ? '/' : $row->URL.'/',
	    			//'active' => $row->Id==$activePageId,
    				//'visible'=> $row->InMenu==1
	    	);
	    	//if ($row->Id==$activePageId) $page['active'] = true;
	    	$rowSet->next();
	    	$nextRow = $rowSet->current();
	    	if ((null!=$nextRow) && ($nextRow->ParentId==$row->Id))
	    	{
	    		$page['pages'] = $this->_parseMenu($activePageId,$rowSet);
	    		$rowSet->next();
	    		$row = $rowSet->current();
	    	}
	    	else
	    		$row = $nextRow;
	    	$result[] = $page;
    	}
		return $result;
    }

    /**
     * Только сохраняет узел
     * @param Admin_Model_TreeNode $node	сохраняемый узел
     * @param bool|array $onlyFields		если истина - сораняются только поля заданные в Admin_Model_TreeNode,
     * 											ложь - все поля получаемые из типа, к которому принадлежит $node,
     * 										если массив - поля из массива 
     */
    public function save(Admin_Model_TreeNode $node,$onlyFields=false){
        $data = $node->toArray($onlyFields);
        if (null === ($id = $node->getId())) {
            throw new Zend_Exception("'Id' must be specified in Admin_Model_TreeNode before calling 'save' method");
        }
        else {
            $this->getDbTable()->update($data, array('id = ?' => $id));
        }
    }
    
    /**
     * Добавляет подузел
     * @param int|Admin_Model_TreeNode $parent	- родительский узел
     * @param Admin_Model_TreeNode $newNode		- добавляемый узел
     * @param bool|array $onlyFields			если истина - сораняются только поля заданные в Admin_Model_TreeNode,
     * 												ложь - все поля получаемые из типа, к которому принадлежит $node,
     * 											если массив - поля из массива
     */
    public function addSubNode($parent, Admin_Model_TreeNode $newNode){
    	$parentNode = ($parent instanceof Admin_Model_TreeNode)? $parent : $this->find($parent);    	
    	$newNode->setLeftId($parentNode->getRightId())
				->setRightId($parentNode->getRightId()+1)
				->setLevel($parentNode->getLevel()+1)
				->setParentId($parentNode->getId());
				
    	$t = $this->getDbTable();
    	$db = $t->getAdapter();
    	$newId = 0;
    	try
    	{
    		//TODO: Check URL existence? but skip achros like '^#dsfsdf$'
    		$db->beginTransaction();
	    	$t->update(array(
	    		'RightId'=>new Zend_Db_Expr('RightId+2'),
	    		'LeftId'=>new Zend_Db_Expr("IF(LeftId > {$parentNode->RightId}, LeftId + 2, LeftId)")
	    		),
	    		"RightId>={$parentNode->RightId}"
	    	);
	    	$parentNode->setRightId($parentNode->getRightId()+2);
			
	    	$data = $newNode->toArray(true);
	    	unset($data['Id']);
	    	$newId = $t->insert($data);
	    	$db->commit();
	    	$newNode->setId($newId);
    	}
    	catch(Exception $e)
    	{
    		$db->rollBack();
    		throw $e; 
    	}
    }
    

    /**
     * Удаляет узел
     * @param int $Id идентификатор узла
     * @return int - кол-во удаленных узлов.
     */
    public function delete($Id){
    	$node = $this->find($Id);
    	if ($node->getId()==$node->getParentId())
    		return 0;
    	$t = $this->getDbTable();
    	$db = $t->getAdapter();
    	try
    	{
    		$db->beginTransaction();
    		$num = $t->delete("LeftId >= {$node->getLeftId()} AND  RightId <= {$node->getRightId()}");
    		//DELETE FROM my_tree WHERE left_key >= $left_key AND  right_ key <= $right_key 
    		$a = $node->getRightId()-$node->getLeftId()+1;
    		$b = $node->getRightId()-$node->getLeftId()+1;
    		$t->update(
    			array(
    				'LeftId'=>new Zend_Db_Expr("IF(LeftId > {$node->getLeftId()}, LeftId-{$a}, LeftId)"),
    				'RightId'=>new Zend_Db_Expr("RightId-{$b}") 
    			),
    			"RightId > {$node->getRightId()}"
    		);
    		//UPDATE my_tree SET left_key = IF(left_key > $left_key, left_key – ($right_key - $left_key  + 1), left_key), right_key = right_key – ($right_key - $left_key + 1) WHERE right_key > $right_key 
    		$db->commit();
    	}
    	catch(Exception $e)
    	{
    		$db->rollBack();
    		$num = 0;
    		throw $e; 
    	}
    	return $num;
    }

    public function fetchURLs(){
    	$t = $this->getDbTable();
    	$rowSet = $t->fetchAll($t->select()->from($t,array('Id','URL'))->where("(ParentId<>Id) AND (URL<>'/')"));
    	$entries = array();
        foreach ($rowSet as $row) {
        	$entries[] = array('Id'=>$row->Id,'URL'=>$row->URL);
        }
    	return $entries;
    }
    
    public function fetchParent($childId){
    	$t = $this->getDbTable();
    	$tableName = $t->info(Zend_Db_Table_Abstract::NAME);
    	
    	$row = $t->fetchRow($t->select()
    							->from(array('t1'=>$tableName),null)
    							->join(array('t2'=>$tableName), 't1.ParentId=t2.Id')
    							->where('t1.Id=?',$childId)
    							);
    							
    	return null!=$row? $this->_createNode($row->toArray()) : null;
    }
    
    protected function _createNode($row){
    	$result = null;
    	if (is_array($row))
    	{
    		if (empty($row['XML']))
    			$result = new Admin_Model_TreeNode($row);
    		else{
    			$xml =new XmlReader();
    			$xml->XML($row['XML']);
    			if ($xml->read()){
    				$className = $xml->name; 
    				if (class_exists($className))
    				{
    					$result = new $className($row);
    					$result->unpack();
    				}
    				else
    					throw new Nippy_Exception("Class '{$className}' doesn't exist");
    			}
    			$xml->close();
    		}
    	}
    	return $result;
    }
    
    public function copySubTree($id,$destId){
    	$m1 = new Admin_Model_TreeNodeMapper( );
		$node = $m1->find($id);
		$nodes = $m1->fetchSubTree($node);
		array_unshift($nodes,$node);
		$this->_insertSubTree($destId,$nodes);
    }
    //protected static $yyy = 0;
    protected function _insertSubTree($newParent,&$nodes){
    	//echo 'IN[ ';
    	$node1 = current($nodes);
    	$node2 = false;
    	while($node1!==false){
    		$node = clone $node1;
    		
			$node->pack();
			$this->addSubNode($newParent,$node);
			echo $node->getName().' R='.$node->getRightId().' ';
			
			$node2 = next($nodes);
			if (($node2!=false) && ($node2->getRightId()<$node1->getRightId()))
			{
				$node2 = $this->_insertSubTree($node,$nodes); //это ребенок, рекурсия
				if ($newParent instanceof Admin_Model_TreeNode)
					$newParent = $newParent->getId();
			}
				
			if (($node2==false) || ($node2->getLevel()==$node1->getLevel()))
				$node1 = $node2; //это либо конец, либо брат
			else
				$node1 = false;	//это родитель, но не наш
    	}
    	//echo ']';
    	//self::$yyy++;
    	//if (self::$yyy==2) exit;
    	return $node2;
    }
	
	public function copyNode($id,$destId){
		$nodes = array($this->find($id));
		$this->_insertSubTree($destId,$nodes);
    }
    
    public function moveNode(){
    	
    	
    }
}

?>